﻿using System;
using System.Collections.Generic;
using System.Linq;
using ControleRepresentante.Logic.Models;
using ControleRepresentante.Logic.Statistics;
using NHibernate;
using NHibernate.Criterion;
using Attribute = ControleRepresentante.Logic.Models.Attribute;

namespace ControleRepresentante.Logic.Repositories
{
    public interface IDetailsHolderRepository<TModel, TStatistics> : IAttributeHolderRepository<TModel, TStatistics>
        where TModel : DetailsHolder 
        where TStatistics : IStatistics
    {
        IEnumerable<string> SearchCidades(string cidade);
        IEnumerable<string> SearchBairros(string bairro);
    }

    public abstract class DetailsHolderRepository<TModel, TStatistics, TError> : AttributeHolderRepository<TModel, TStatistics, TError>, IDetailsHolderRepository<TModel, TStatistics>
        where TModel : DetailsHolder 
        where TStatistics : IStatistics
    {
        protected DetailsHolderRepository(IRepositoryConfiguration repositoryConfiguration, ISession session)
            : base(repositoryConfiguration, session)
        {
        }

        private static ICriteria AddCidadeAndBairroSearchCriteria(ICriteria criteria, string cidade, string bairro, string tableAlias)
        {
            bool isCidadeNullOrEmpty = String.IsNullOrEmpty(cidade);
            bool isBairroNullOrEmpty = String.IsNullOrEmpty(bairro);
            if (!isCidadeNullOrEmpty || !isBairroNullOrEmpty)
            {
                DetachedCriteria detachedCriteria = DetachedCriteria.For<Endereco>()
                    .SetProjection(Projections.Property<Endereco>(x => x.Id))
                    .Add(Restrictions.EqProperty(tableAlias + ".Id", Projections.Property<Endereco>(x => x.DetailsHolder.Id)));

                if (!isBairroNullOrEmpty)
                {
                    detachedCriteria = detachedCriteria.Add(
                        Restrictions.InsensitiveLike(Projections.Property<Endereco>(x => x.Bairro), "%" + bairro.ToLower() + "%"));
                }
                if (!isCidadeNullOrEmpty)
                {
                    detachedCriteria = detachedCriteria.Add(
                        Restrictions.InsensitiveLike(Projections.Property<Endereco>(x => x.Cidade), "%" + cidade.ToLower() + "%"));
                }

                criteria = criteria.Add(Subqueries.Exists(detachedCriteria));
            }
            return criteria;
        }

        protected ICriteria CreateCriteria(string cidade, string bairro, Dictionary<Attribute, string> attributeMap, string tableAlias)
        {
            ICriteria criteria = CreateCriteria(attributeMap, tableAlias);

            criteria = AddCidadeAndBairroSearchCriteria(criteria, cidade, bairro, tableAlias);

            return criteria;
        }

        public IEnumerable<string> SearchCidades(string cidade)
        {
            return Session.QueryOver<Endereco>()
                .Where(Restrictions.InsensitiveLike(Projections.Property<Endereco>(x => x.Cidade), "%" + cidade.ToLower() + "%"))
                .Inner.JoinQueryOver(x => x.DetailsHolder)
                .And(x => x.Representante.Id == GetRepresentanteId())
                .Select(x => x.Cidade)
                .Future<string>()
                .Distinct();
        }

        public IEnumerable<string> SearchBairros(string bairro)
        {
            return Session.QueryOver<Endereco>()
                .Where(Restrictions.InsensitiveLike(Projections.Property<Endereco>(x => x.Bairro), "%" + bairro.ToLower() + "%"))
                .Inner.JoinQueryOver(x => x.DetailsHolder)
                .And(x => x.Representante.Id == GetRepresentanteId())
                .Select(x => x.Bairro)
                .Future<string>()
                .Distinct();
        }

        protected void SetContato<T>(T detailsHolder, IEnumerable<Contato> contatos, ContatoType contatoType, Action<T, string> setContatoValue)
            where T : DetailsHolder
        {
            Contato contato = contatos.FirstOrDefault(x => x.Tipo == contatoType && x.DetailsHolder.Id == detailsHolder.Id);
            setContatoValue(detailsHolder, contato == null ? String.Empty : contato.Valor);
        }

        protected void SetEndereco<T>(T detailsHolder, IEnumerable<Endereco> enderecos, EnderecoType enderecoType, Action<T, Endereco> setEnderecoValue)
            where T : DetailsHolder
        {
            Endereco endereco = enderecos.FirstOrDefault(x => x.Tipo == enderecoType && x.DetailsHolder.Id == detailsHolder.Id);
            setEnderecoValue(detailsHolder, endereco);
        }

        protected IEnumerable<Contato> GetContatos(IEnumerable<DetailsHolder> models, params ContatoType[] types)
        {
            return Session.QueryOver<Contato>()
                .Where(x => x.DetailsHolder.Id.IsIn(models.Select(y => y.Id).Cast<object>().ToArray()))
                .And(x => x.Principal)
                .And(x => x.Tipo.IsIn(types.Cast<object>().ToArray()))
                .List();
        }

        protected IEnumerable<Endereco> GetEnderecos(IEnumerable<DetailsHolder> models, params EnderecoType[] types)
        {
            return Session.QueryOver<Endereco>()
                .Where(x => x.DetailsHolder.Id.IsIn(models.Select(y => y.Id).Cast<object>().ToArray()))
                .And(x => x.Principal)
                .And(x => x.Tipo.IsIn(types.Cast<object>().ToArray()))
                .List();
        }

        protected override void SetProperties(IEnumerable<TModel> models)
        {
            if (models.Any())
            {
                IEnumerable<Endereco> enderecos = GetEnderecos(models, EnderecoType.Comercial);
                IEnumerable<Contato> contatos = GetContatos(models, ContatoType.FaxComercial, ContatoType.TelefoneComercial, ContatoType.Email, ContatoType.Celular);

                foreach (TModel model in models)
                {
                    SetContato(model, contatos, ContatoType.FaxComercial, (m, c) => m.ContatoFax = c);
                    SetContato(model, contatos, ContatoType.TelefoneComercial, (m, c) => m.ContatoTelefone = c);
                    SetContato(model, contatos, ContatoType.Email, (m, c) => m.ContatoEmail = c);
                    SetContato(model, contatos, ContatoType.Celular, (m, c) => m.ContatoCelular = c);
                    SetEndereco(model, enderecos, EnderecoType.Comercial, (m, e) =>
                    {
                        if (e == null)
                        {
                            m.EnderecoRua = String.Empty;
                            m.EnderecoNumero = String.Empty;
                            m.EnderecoComplemento = String.Empty;
                            m.EnderecoBairro = String.Empty;
                            m.EnderecoCEP = String.Empty;
                            m.EnderecoCidade = String.Empty;
                            m.EnderecoEstado = String.Empty;
                        }
                        else
                        {
                            m.EnderecoRua = e.Rua;
                            m.EnderecoNumero = e.Numero;
                            m.EnderecoComplemento = e.Complemento;
                            m.EnderecoBairro = e.Bairro;
                            m.EnderecoCEP = e.CEP;
                            m.EnderecoCidade = e.Cidade;
                            m.EnderecoEstado = e.Estado;
                        }
                    });
                }
            }
        }
    }
}